package comlib.chisel

import chisel3._


class ComparatorBase [D<:Data] (kwid:Int, gen:D) extends Bundle {

    /** Compare key word */
    val key = Output(UInt(kwid.W))
    /** Bypass data */
    val data = Output(chiselTypeOf(gen))

    override def cloneType = (new ComparatorBase(kwid,gen)).asInstanceOf[this.type]

    def > (another:ComparatorBase[D]) = key > another.key
    def >= (another:ComparatorBase[D]) = key >= another.key
    def < (another:ComparatorBase[D]) = key < another.key
    def <= (another:ComparatorBase[D]) = key <= another.key
    def === (another:ComparatorBase[D]) = key === another.key
    def =/= (another:ComparatorBase[D]) = key =/= another.key
}


class ComparatorWithoutValid [D<:Data] (kwid:Int, gen:D) extends ComparatorBase(kwid,gen) {

    override def cloneType = (new ComparatorWithoutValid(kwid,gen)).asInstanceOf[this.type]

    def compare(another:ComparatorWithoutValid[D]) : SInt = {
        val ret = Wire(SInt())

        when( key === another.key ){
            ret := 0.S
        }.elsewhen( key > another.key ){
            ret := 1.S
        }.otherwise{
            ret := -1.S
        }

        ret
    }

    def cmp( f:(UInt,UInt)=>Bool=(a:UInt,b:UInt)=>a>=b ): ComparatorWithoutValid[D]=>ComparatorWithoutValid[D] = (another:ComparatorWithoutValid[D]) => {
        Mux( f(key, another.key), this, another )
    }

    def max( another:ComparatorWithoutValid[D], f:(UInt,UInt)=>Bool=(a:UInt,b:UInt)=>a>=b ) = cmp( f(_,_) )( another )

    override def toPrintable: Printable = {
        p"(${key}, ${data})"
    }
}



/** Compare Bundle encapsulation
  *
  * @tparam D Data type
  *
  * @param gen Comparator bundle's data type reference signal
  * @see object Comparator for instance a Comparator 
  */
class ComparatorWithValid[D<:Data] (kwid:Int, gen:D) extends ComparatorBase(kwid, gen) {

    val vld = Output(Bool())

    def compare(another:ComparatorWithValid[D]) : SInt = {
        val ret = Wire(SInt())

        when( vld && another.vld ){
            when( key === another.key ){
                ret := 0.S
            }.elsewhen( key > another.key ){
                ret := 1.S
            }.otherwise{
                ret := -1.S
            }
        }.elsewhen( vld ){
            ret := 2.S
        }.elsewhen( another.vld ){
            ret := -2.S
        }.otherwise{
            ret := 3.S
        }

        ret
    }

    override def cloneType = (new ComparatorWithValid(kwid,gen)).asInstanceOf[this.type]

    /** Compare and select
      * 
      * @param another Another Comparator[T] for compare
      * @param f Compare function
      * @return Select `this` if `f(this.key, another.key)` is true.B, else select `another`
      */
    def max( another:ComparatorWithValid[D], f:(UInt,UInt)=>Bool=(a:UInt,b:UInt)=>a>=b ) = cmp( _>=_ )( another )

  def cmp( f:(UInt,UInt)=>Bool=(a:UInt,b:UInt)=>a>=b ): ComparatorWithValid[D]=>ComparatorWithValid[D] = (another:ComparatorWithValid[D]) => {

    Mux( vld&&another.vld,
      Mux( f(key, another.key), this, another ),
      Mux( vld, 
        this,
        Mux( another.vld, another, Comparator( false.B, key, data ))
      )
    )

  }

  override def toPrintable: Printable = {
    p"(${vld}, ${key}, ${data})"
  }

}


object Comparator{
    /** Create and Initial an Comparator Bundle
      *
      * @param vld Comparator valid signal
      * @param key Comparator key signal
      * @param data Comprator bypass information signal
      * @note class Comparator is a chisel type, like UInt type. But object Comparator is a factory function of Comparator, like WireInit
      */
    def apply[D<:Data](vld:Bool, key:UInt, data:D) ={
        // val ret = Wire(new Comparator(chiselTypeOf(data)))
        val ret = Wire(new ComparatorWithValid(key.getWidth, data))
        ret.vld := vld
        ret.key := key
        ret.data := data
        ret
    }

    /** Create and Initial an Comparator Bundle
      *
      * @param key Comparator key signal
      * @param data Comprator bypass information signal
      * @note class Comparator is a chisel type, like UInt type. But object Comparator is a factory function of Comparator, like WireInit
      */
    def apply[D<:Data](key:UInt, data:D) = {
        val ret = Wire(new ComparatorWithoutValid(key.getWidth, data))
        ret.key := key
        ret.data := data
        ret
    }
}

